<script setup>
import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue';

// Props
const props = defineProps({
  items: { type: Array, required: true }, // 数据源数组
  itemHeight: { type: Number, default: 22 }, // 固定行高
  overscan: { type: Number, default: 10 }, // 预渲染行数
  getItemKey: { type: Function, default: (item, index) => index }, // 获取稳定key的函数
  height: { type: [String, Number], default: '100%' }, // 容器高度
});

// Emits
const emit = defineEmits(['scroll']);

// Refs
const containerRef = ref(null);
const scrollTop = ref(0);
const viewportHeight = ref(0);
const resizeObserverRef = ref(null);

// 使用 requestAnimationFrame 批处理滚动事件
let rafId = null;
let pendingScrollUpdate = false;

function scheduleScrollUpdate() {
  if (pendingScrollUpdate) return;
  pendingScrollUpdate = true;
  
  rafId = requestAnimationFrame(() => {
    const el = containerRef.value;
    if (el) {
      scrollTop.value = el.scrollTop;
    }
    pendingScrollUpdate = false;
    emit('scroll', { scrollTop: scrollTop.value, viewportHeight: viewportHeight.value });
  });
}

function onScroll() {
  scheduleScrollUpdate();
}

// 计算属性
const totalRows = computed(() => props.items.length);
const totalHeight = computed(() => totalRows.value * props.itemHeight);

const startIndex = computed(() => {
  const start = Math.floor(scrollTop.value / props.itemHeight) - props.overscan;
  return Math.max(0, start);
});

const endIndex = computed(() => {
  const end = Math.floor((scrollTop.value + viewportHeight.value) / props.itemHeight) + props.overscan;
  return Math.min(totalRows.value - 1, end);
});

// 行对象复用缓存
const rowCache = new Map();

const visibleItems = computed(() => {
  const result = [];
  const start = startIndex.value;
  const end = endIndex.value;
  
  for (let i = start; i <= end && i < props.items.length; i++) {
    let row = rowCache.get(i);
    if (!row) {
      row = { index: i, item: props.items[i], key: props.getItemKey(props.items[i], i) };
      rowCache.set(i, row);
    } else {
      // 更新缓存的行对象
      row.index = i;
      row.item = props.items[i];
      row.key = props.getItemKey(props.items[i], i);
    }
    result.push(row);
  }
  
  return result;
});

// 清理不再需要的缓存
watch([startIndex, endIndex], ([newStart, newEnd], [oldStart, oldEnd]) => {
  if (oldStart !== undefined && oldEnd !== undefined) {
    // 清理超出可见范围的缓存
    const cacheKeys = Array.from(rowCache.keys());
    for (const key of cacheKeys) {
      if (key < newStart - props.overscan || key > newEnd + props.overscan) {
        rowCache.delete(key);
      }
    }
  }
});

// 滚动到指定索引
function scrollToIndex(index, behavior = 'auto') {
  const container = containerRef.value;
  if (!container) return;
  
  const targetScrollTop = index * props.itemHeight;
  container.scrollTo({ top: targetScrollTop, behavior });
}

// 确保指定索引可见（带边距，避免底部被遮挡）
function ensureIndexVisible(index, padding = 6) {
  const container = containerRef.value;
  if (!container) return;
  
  const top = container.scrollTop;
  const height = container.clientHeight;
  const itemTop = index * props.itemHeight;
  const itemBottom = itemTop + props.itemHeight;
  const maxScroll = Math.max(0, container.scrollHeight - height);
  
  if (itemTop < top + padding) {
    container.scrollTop = Math.max(0, itemTop - padding);
  } else if (itemBottom > top + height - padding) {
    container.scrollTop = Math.min(maxScroll, itemBottom - height + padding);
  }
}

// 生命周期
onMounted(() => {
  const el = containerRef.value;
  if (el) {
    scrollTop.value = el.scrollTop;
    viewportHeight.value = el.clientHeight;
    el.addEventListener('scroll', onScroll, { passive: true });
  }
  
  // 监听容器尺寸变化
  try {
    const ro = new ResizeObserver(() => {
      const container = containerRef.value;
      if (container) {
        viewportHeight.value = container.clientHeight;
      }
    });
    if (el) ro.observe(el);
    resizeObserverRef.value = ro;
  } catch (e) {
    console.warn('ResizeObserver not supported:', e);
  }
});

onBeforeUnmount(() => {
  const el = containerRef.value;
  if (el) {
    try {
      el.removeEventListener('scroll', onScroll);
    } catch (e) {
      console.warn('Error removing scroll listener:', e);
    }
  }
  
  if (resizeObserverRef.value) {
    try {
      resizeObserverRef.value.disconnect();
    } catch (e) {
      console.warn('Error disconnecting ResizeObserver:', e);
    }
  }
  
  if (rafId) {
    cancelAnimationFrame(rafId);
  }
  
  // 清理缓存
  rowCache.clear();
});

// 暴露方法
defineExpose({
  scrollToIndex,
  ensureIndexVisible,
  getScrollTop: () => scrollTop.value,
  getViewportHeight: () => viewportHeight.value,
});
</script>

<template>
  <div
    ref="containerRef"
    class="virtual-list-container"
    :style="{ height: typeof height === 'number' ? height + 'px' : height }"
  >
    <!-- 总高度占位容器 -->
    <div
      class="virtual-list-spacer"
      :style="{ height: totalHeight + 'px', position: 'relative' }"
    >
      <!-- 可见行渲染 -->
      <div
        v-for="row in visibleItems"
        :key="row.key"
        class="virtual-list-item"
        :style="{
          position: 'absolute',
          left: 0,
          right: 0,
          top: (row.index * itemHeight) + 'px',
          height: itemHeight + 'px'
        }"
      >
        <slot
          :item="row.item"
          :index="row.index"
          :key="row.key"
        >
          <!-- 默认渲染 -->
          <div class="virtual-list-default-item">
            {{ row.item }}
          </div>
        </slot>
      </div>
    </div>
  </div>
</template>

<style scoped>
.virtual-list-container {
  overflow-y: auto;
  overflow-x: hidden;
}

/* 应用项目的主题感知滚动条样式 */
.virtual-list-container {
  scrollbar-width: thin;
}

html:not(.dark) .virtual-list-container {
  scrollbar-color: rgba(100,116,139,.6) transparent;
}

html.dark .virtual-list-container {
  scrollbar-color: rgba(148,163,184,.5) transparent;
}

.virtual-list-container::-webkit-scrollbar {
  height: 8px;
  width: 8px;
}

html:not(.dark) .virtual-list-container::-webkit-scrollbar-thumb {
  background-color: rgba(100,116,139,.55);
  border-radius: 8px;
  border: 2px solid transparent;
  background-clip: padding-box;
}

html:not(.dark) .virtual-list-container::-webkit-scrollbar-thumb:hover {
  background-color: rgba(100,116,139,.75);
}

html.dark .virtual-list-container::-webkit-scrollbar-thumb {
  background-color: rgba(148,163,184,.45);
  border-radius: 8px;
  border: 2px solid transparent;
  background-clip: padding-box;
}

html.dark .virtual-list-container::-webkit-scrollbar-thumb:hover {
  background-color: rgba(148,163,184,.65);
}

.virtual-list-container::-webkit-scrollbar-corner {
  background: transparent;
}

.virtual-list-spacer {
  min-height: 100%;
}

.virtual-list-item {
  contain: layout style;
}

.virtual-list-default-item {
  padding: 4px 8px;
  border-bottom: 1px solid #eee;
}
</style>